package com.dhecp.project.flowable.service.impl;

import com.dhecp.common.core.domain.AjaxResult;
import com.dhecp.common.core.page.PageDomain;
import com.dhecp.common.core.page.TableSupport;
import com.dhecp.common.core.text.Convert;
import com.dhecp.common.utils.MessageUtils;
import com.dhecp.common.utils.StringUtils;
import com.dhecp.common.utils.TypeUtils;
import com.dhecp.project.flowable.extend.CustomBpmnJsonConverter;
import com.dhecp.project.flowable.service.ActProcessModelService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pagehelper.Page;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.editor.constants.ModelDataJsonConstants;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;

import static org.flowable.editor.constants.ModelDataJsonConstants.MODEL_ID;
import static org.flowable.editor.constants.ModelDataJsonConstants.MODEL_NAME;

/**
 * @ClassName ActProcessModelServiceImpl
 * @Description TODO
 * @Author alula
 * @Date 2019-11-27 10:13
 */
@Service
public class ActProcessModelServiceImpl implements ActProcessModelService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Page<Model> queryModelList(Map<String, Object> params) {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();

        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (pageDomain.getOrderByColumn().contains("createTime") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByCreateTime().desc();
        }
        if (pageDomain.getOrderByColumn().contains("createTime") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByCreateTime().asc();
        }
        if (pageDomain.getOrderByColumn().contains("key") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByModelKey().desc();
        }
        if (pageDomain.getOrderByColumn().contains("key") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByModelKey().asc();
        }
        if (pageDomain.getOrderByColumn().contains("name") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByModelName().desc();
        }
        if (pageDomain.getOrderByColumn().contains("name") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByModelName().asc();
        }
        if (pageDomain.getOrderByColumn().contains("category") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByModelCategory().desc();
        }
        if (pageDomain.getOrderByColumn().contains("category") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByModelCategory().asc();
        }
        if (pageDomain.getOrderByColumn().contains("version") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByModelVersion().desc();
        }
        if (pageDomain.getOrderByColumn().contains("version") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByModelVersion().asc();
        }
        if (pageDomain.getOrderByColumn().contains("lastUpdateTime") && pageDomain.getOrderBy().contains("desc")){
            modelQuery.orderByLastUpdateTime().desc();
        }
        if (pageDomain.getOrderByColumn().contains("lastUpdateTime") && pageDomain.getOrderBy().contains("asc")){
            modelQuery.orderByLastUpdateTime().asc();
        }
        // 条件过滤
        if (params.containsKey("key") && !StringUtils.isEmpty(params.get("key").toString())){
            modelQuery.modelKey(params.get("key").toString());
        }
        if (params.containsKey("name") && !StringUtils.isEmpty(params.get("name").toString())){
            modelQuery.modelNameLike("%" + params.get("name").toString() + "%");
        }
        if (params.containsKey("category") && !StringUtils.isEmpty(params.get("category").toString())){
            modelQuery.modelCategoryLike("%" + params.get("category").toString() + "%");
        }
        if (params.containsKey("id") && !StringUtils.isEmpty(params.get("id").toString())){
            modelQuery.modelId(params.get("id").toString());
        }
        if (params.containsKey("version") && !StringUtils.isEmpty(params.get("version").toString())){
            int intVersion = TypeUtils.getSafeInt(params.get("version").toString(), 0);
            if(intVersion  > 0) {
                modelQuery.modelVersion(intVersion);
            }else{
                modelQuery.modelVersion(Integer.MAX_VALUE);
            }
        }
        List<Model> resultList = modelQuery.listPage((pageNum - 1) * pageSize, pageSize);
        Page<Model> list = new Page<>();
        list.addAll(resultList);
        list.setTotal(modelQuery.count());
        list.setPageNum(pageNum);
        list.setPageSize(pageSize);
        return list;
    }

    @Override
    public Model queryModelById(String modelId) {
        return repositoryService.getModel(modelId);
    }

    @Override
    public Model getNewModel() {
        return repositoryService.newModel();
    }

    @Override
    public ObjectNode queryWrapModelById(String modelId) {
        ObjectNode modelNode = null;
        Model model = repositoryService.getModel(modelId);
        if (model != null)
        {
            try
            {
                if (org.apache.commons.lang3.StringUtils.isNotEmpty(model.getMetaInfo()))
                {
                    modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                }
                else
                {
                    modelNode = objectMapper.createObjectNode();
                    modelNode.put(MODEL_NAME, model.getName());
                }
                modelNode.put(MODEL_ID, model.getId());
                ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
                modelNode.set("model", editorJsonNode);

            }
            catch (Exception e)
            {
                throw new FlowableException("创建模型json失败，模型ID=" + modelId,e);
            }
        }
        return modelNode;
    }

    @Override
    public AjaxResult addModel(Model model, String description) throws Exception {
        if (model  == null){
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", model));
        }
        Model oldModel = repositoryService.createModelQuery().modelKey(model.getKey()).singleResult();
        if(oldModel != null){
            return AjaxResult.error(MessageUtils.message("flowable.model.error.modelexistsfail"));
        }
        repositoryService.saveModel(model);
        String modelId = model.getId();
        // 完善ModelEditorSource
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("modelId", model.getId());
        //editorNode.put("resourceId", "canvas");
        editorNode.put("key", model.getKey());
        editorNode.put("name", model.getName());
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        stencilSetNode.put("url","../editor/stencilsets/bpmn2.0/bpmn2.0.json");
        editorNode.put("stencilset", stencilSetNode);

        ObjectNode stencilNode = objectMapper.createObjectNode();
        stencilNode.put("id","BPMNDiagram");
        stencilNode.put("stencil", stencilNode);

        ObjectNode properties = objectMapper.createObjectNode();
        properties.put("process_id", model.getKey());
        properties.put("name", model.getName());
        properties.put("documentation",description);
        properties.put("process_namespace","http://www.flowable.org/processdef");
        properties.put("isexecutable","True");
        properties.put("iseagerexecutionfetch","false");

        editorNode.replace("properties", properties);

        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, model.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        description = org.apache.commons.lang3.StringUtils.defaultString(description);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        repositoryService.addModelEditorSource(modelId, editorNode.toString().getBytes("utf-8"));
        return new AjaxResult(AjaxResult.Type.SUCCESS, MessageUtils.message("module.success.issuccess"), model.getId());
    }

    @Override
    public AjaxResult updateModel(Model model, String json_xml, String svg_xml) throws Exception {
        if (model  == null){
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", model));
        }
        try {
            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
            return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
        }catch (Exception e){
            return AjaxResult.error(MessageUtils.message("module.error.iserror"));
        }
    }

    @Override
    public AjaxResult deleteModelByIds(String ids) {
        if (StringUtils.isEmpty(ids)) {
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", ids));
        }
        String[] modelIds = Convert.toStrArray(ids);
        int counter = 0;
        for (String modelId : modelIds)
        {
            repositoryService.deleteModel(modelId);
            counter++;
        }
        if (counter == 0){
            return AjaxResult.error(MessageUtils.message("module.error.iserror"));
        }
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    public AjaxResult deployModel(String modelId) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
        if (bytes == null)
        {
            return AjaxResult.error(MessageUtils.message("flowable.model.error.modeldatafail"));
        }

        JsonNode modelNode = new ObjectMapper().readTree(bytes);
        BpmnJsonConverter jsonConverter = new CustomBpmnJsonConverter();
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(modelNode);
        bpmnModel.setTargetNamespace(modelData.getCategory());
        if (bpmnModel.getProcesses().size() == 0)
        {
            return AjaxResult.error(MessageUtils.message("flowable.model.error.modelprocessfail"));
        }
        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);

        // 发布流程
        String processName = modelData.getName() + ".bpmn20.xml";
        Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).category(modelData.getCategory()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
        modelData.setDeploymentId(deployment.getId());
        repositoryService.saveModel(modelData);
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    public AjaxResult importModel(String editor, String fileName) throws UnsupportedEncodingException, XMLStreamException {
        if(StringUtils.isEmpty(editor)){
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", editor));
        }
        byte[] bytes = null;
        BpmnModel bpmnModel = null;
        String key = "";
        String name = "";
        String category = "";
        String description = "";
        if (editor != null && editor.length() > 0) {
            bytes = editor.getBytes("utf-8");

            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStreamReader xmlIn = new InputStreamReader(new ByteArrayInputStream(bytes), "UTF-8");
            XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
            bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

            key = bpmnModel.getMainProcess().getId();
            name = bpmnModel.getMainProcess().getName();
            category = bpmnModel.getMainProcess().getDocumentation();
            description = bpmnModel.getMainProcess().getDocumentation();
        }
        Model newModel = getNewModel();
        Model oldModel = repositoryService.createModelQuery().modelKey(key).orderByModelVersion().desc().singleResult();
        if(oldModel != null){
            newModel.setVersion(oldModel.getVersion()+1);
        }
        description = StringUtils.isEmpty(description) ? name:description;
        newModel.setKey(key);
        newModel.setName(name);
        newModel.setCategory(category);
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode metaInfo = objectMapper.createObjectNode();
        metaInfo.put("key", key);
        metaInfo.put(ModelDataJsonConstants.MODEL_ID, newModel.getId());
        metaInfo.put(ModelDataJsonConstants.MODEL_NAME, name);
        metaInfo.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        metaInfo.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        newModel.setMetaInfo(metaInfo.toString());
        repositoryService.saveModel(newModel);
        if (bpmnModel != null) {
            String modelId = newModel.getId();
            // 完善ModelEditorSource
            BpmnJsonConverter converter = new CustomBpmnJsonConverter();
            ObjectNode editorNode = converter.convertToJson(bpmnModel);
            editorNode.put("modelId", newModel.getId());
            //editorNode.put("resourceId", "canvas");
            editorNode.put("key", newModel.getKey());
            editorNode.put("name", newModel.getName());
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
            stencilSetNode.put("url", "../editor/stencilsets/bpmn2.0/bpmn2.0.json");
            editorNode.put("stencilset", stencilSetNode);

            ObjectNode stencilNode = objectMapper.createObjectNode();
            stencilNode.put("id", "BPMNDiagram");
            stencilNode.put("stencil", stencilNode);

            ObjectNode properties = objectMapper.createObjectNode();
            properties.put("process_id", newModel.getKey());
            properties.put("name", newModel.getName());
            properties.put("documentation", description);
            properties.put("process_namespace", "http://www.flowable.org/processdef");
            properties.put("isexecutable", "True");
            properties.put("iseagerexecutionfetch", "false");
            editorNode.replace("properties", properties);
            ObjectNode modelObjectNode = objectMapper.createObjectNode();
            modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, newModel.getName());
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            description = org.apache.commons.lang3.StringUtils.defaultString(description);
            modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
            repositoryService.addModelEditorSource(modelId, editorNode.toString().getBytes("utf-8"));
        }
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    public byte[] getModelEditorSource(String modelId) {
        return repositoryService.getModelEditorSource(modelId);
    }
}
